using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System.Collections.ObjectModel;

namespace Oops.Xna.Framework.GameState
{
	/// <summary>
	/// A game component that manages <see cref="GameScreen"/> instances.
	/// </summary>
	public class GameStateComponent2 : DrawableGameComponent, IGameStateService2, IInternalGameStateService2
	{
		#region ActiveGameScreenCollection Declaration

		public class ActiveGameScreenCollection : Collection<GameScreen2>
		{
			private GameStateComponent2 component;

			internal ActiveGameScreenCollection(GameStateComponent2 component)
			{
				// The component parameter must be specified.
				if (component == null)
				{
					throw new ArgumentNullException("component");
				}

				// Hold onto parameters.
				this.component = component;
			}
			
			#region Collection<GameScreen> Members

			protected override void ClearItems()
			{
				throw new InvalidOperationException();
			}

			protected override void InsertItem(int index, GameScreen2 item)
			{
				// Can only add screens that are already registered.
				if (!this.component.Screens.Contains(item))
				{
					throw new InvalidOperationException();
				}

				// The screen will need to be notified that it was just added.
				item.IsNew = true;

				// Call inherited method.
				base.InsertItem(index, item);
			}

			protected override void RemoveItem(int index)
			{
				// Skip if this component is not initialized.
				if (this.component.initialized)
				{
					// Get the removing item.
					GameScreen2 item = this.Items[index];

					// Make sure it doesn't get updated.
					this.component.updateableScreens.Remove(item);
				}

				// Call inherited method.
				base.RemoveItem(index);
			}

			protected override void SetItem(int index, GameScreen2 item)
			{
				throw new InvalidOperationException();
			}

			#endregion
		}

		#endregion

		private GameScreen2 activeScreen;

		private GameScreenCollection2 screens;
		private ActiveGameScreenCollection activeScreens;

		private bool initialized;
		private List<IGameComponent> notYetInitialized = new List<IGameComponent>();

		private List<GameScreen2> updateableScreens = new List<GameScreen2>();
		private List<GameScreen2> drawableScreens = new List<GameScreen2>();

		/// <summary>
		/// Initializes a new instance of the <see cref="GameStateComponent"/> class.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/>that the game component should be attached to.</param>
		public GameStateComponent2(Game game) : base(game)
		{
			// The game parameter must be specified.
			if (game == null)
			{
				throw new ArgumentNullException("game");
			}

			// Create the active game screen collection.
			this.activeScreens = new ActiveGameScreenCollection(this);

			// Create the game screen collection and subscribe to some of it's events.
			this.screens = new GameScreenCollection2();
			this.screens.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs<GameScreen2>>(this.OnGameScreenAdded);
			this.screens.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs<GameScreen2>>(this.OnGameScreenRemoved);
		}

		#region GameComponent Members

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					// Remove self from the service container.
					if (this.Game.Services.GetService(typeof(IGameStateService2)) == this)
					{
						this.Game.Services.RemoveService(typeof(IGameStateService2));
						this.Game.Services.RemoveService(typeof(IInternalGameStateService));
					}

					// Copy the game screens to an array for index-based iteration.
					GameScreen2[] screens = new GameScreen2[this.screens.Count];
					this.screens.CopyTo(screens, 0);

					// Dispose of all game screens.
					for (int i = 0; i < screens.Length; i++)
					{
						IDisposable disposable = screens[i] as IDisposable;
						if (disposable != null)
						{
							this.activeScreen = screens[i];
							disposable.Dispose();
						}
					}
				}
			}

			// Call inherited method.
			base.Dispose(disposing);
		}

		#endregion

		#region IGameComponent Members

		public override void Initialize()
		{
			// Add self to the service container.
			this.Game.AddService<IGameStateService2>(this, true);
			this.Game.AddService<IInternalGameStateService2>(this, true);

			// Initialize all necessary behaviors.
			while (this.notYetInitialized.Count != 0)
			{
				this.activeScreen = this.notYetInitialized[0] as GameScreen2;
				this.notYetInitialized[0].Initialize();
				this.notYetInitialized.RemoveAt(0);
			}

			// Call inherited method.
			base.Initialize();

			// Set the initialized flag.
			this.initialized = true;
		}

		#endregion

		#region IUpdateable

		public override void Update(GameTime gameTime)
		{
			// Copy the active screens to a temporary list.
			this.updateableScreens.Clear();
			for (int i = 0; i < this.activeScreens.Count; i++)
			{
				this.updateableScreens.Add(this.activeScreens[i]);
			}

			// Iterate until all updateable screens are processed.
			while (this.updateableScreens.Count > 0)
			{
				// Get the next screen on the stack.
				GameScreen2 gameScreen = this.updateableScreens[this.updateableScreens.Count - 1];
				this.updateableScreens.RemoveAt(this.updateableScreens.Count - 1);

				// Notify the screen that it's new.
				if (gameScreen.IsNew)
				{
					gameScreen.LoadDynamicContent();
					gameScreen.Enter(gameTime, true);
					gameScreen.IsNew = false;
				}

				// Get the state the screen.
				this.activeScreen = gameScreen;
				GameScreenState state = gameScreen.GetTransition(gameTime);

				switch (state)
				{
					case GameScreenState.Active:
						// Update the game screen.
						if (gameScreen.Enabled)
						{
							gameScreen.Update(gameTime);
						}

						break;

					case GameScreenState.Enter:
						// Enter the the screen.
						gameScreen.Enter(gameTime, false);
						gameScreen.IsNew = false;

						break;

					case GameScreenState.Exit:
						// Remove the screen if the game screen is finished.
						this.activeScreens.Remove(gameScreen);
						gameScreen.Exit(gameTime);
						gameScreen.UnloadDynamicContent();

						break;

					case GameScreenState.Terminate:
						// Remove the screen if the game screen is finished.
						this.activeScreens.Remove(gameScreen);
						this.screens.Remove(gameScreen);
						gameScreen.Exit(gameTime);
						gameScreen.UnloadDynamicContent();

						gameScreen.Dispose();

						break;
				}
			}

			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion 

		#region IDrawable

		public override void Draw(GameTime gameTime)
		{
			// Copy the active screens to a temporary list.
			this.drawableScreens.Clear();
			for (int i = 0; i < this.activeScreens.Count; i++)
			{
				if (this.activeScreens[i].Visible)
				{
					this.drawableScreens.Add(this.activeScreens[i]);
				}
			}

			// Iterate on all active screens.
			for (int i = 0; i < this.drawableScreens.Count; i++)
			{
				// Get the next screen on the stack.
				GameScreen2 gameScreen = this.drawableScreens[i];

				// Draw the screen.
				this.activeScreen = gameScreen;
				gameScreen.BeforeDraw(gameTime);
			}

			// Iterate on all active screens.
			for (int i = 0; i < this.drawableScreens.Count; i++)
			{
				// Get the next screen on the stack.
				GameScreen2 gameScreen = this.drawableScreens[i];

				// Draw the screen.
				this.activeScreen = gameScreen;
				gameScreen.Draw(gameTime);
			}

			// Call inherited method.
			base.Draw(gameTime);
		}

		#endregion

		#region IGameStateService Members

		public GameScreenCollection2 Screens
		{
			get { return this.screens; }
		}

		public IList<GameScreen2> ActiveScreens
		{
			get { return this.activeScreens; }
		}

		#endregion

		#region IInternalGameStateService Members

		GameScreen2 IInternalGameStateService2.ActiveScreen
		{
			get { return this.activeScreen; }
		}

		#endregion

		private void OnGameScreenAdded(object sender, GameComponentCollectionEventArgs<GameScreen2> e)
		{
			// Initialize or add the game screen to the initialization pending list.
			if (this.initialized)
			{
				this.activeScreen = e.Item;
				e.Item.Initialize();
			}
			else
			{
				this.notYetInitialized.Add(e.Item);
			}
		}

		private void OnGameScreenRemoved(object sender, GameComponentCollectionEventArgs<GameScreen2> e)
		{
			// Remove the game screen from the initialization pending list.
			if (!this.initialized)
			{
				this.notYetInitialized.Remove(e.Item);
			}
		}
	}
}

